package com.njbw.redis.util;

import com.njbw.redis.exception.ShareLockException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * redis 分布式锁工具类
 *
 * 超时机制、自旋、key 的过期销毁
 */
@Component
public class RedisShareLockUtil {

    @Resource
    private RedisUtil redisUtil;

    private final static Long TIME_OUT = 10000L;

    /**
     * 加锁
     *
     * requestId 用来辨别当前锁是否是该业务加过的
     * time 是为了防止死锁
     */
    public boolean lock(String lockKey, String requestId, Long time) {
        // 1.参数的校验
        if (StringUtils.isBlank(lockKey) || StringUtils.isBlank(requestId) || time <= 0) {
            throw new ShareLockException("分布式锁-加锁参数异常");
        }
        // 2.加锁可以有自旋，适用于业务比较小的地方
        long currentTime = System.currentTimeMillis();
        // 超时时间
        long outTime = currentTime + TIME_OUT;
        Boolean result = false;
        while(currentTime < outTime) {
            // 3.借助 redis 的 setNx 来进行锁的设置
            result = redisUtil.setNx(lockKey, requestId, time, TimeUnit.MILLISECONDS);
            if (result) {
                return result;
            }
            // set expire，需要使用 setNx 来保证原子性，当然也可以使用 LUR 脚本的方式
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            currentTime = System.currentTimeMillis();
        }
        return result;
    }

    /**
     * 解锁
     */
    public boolean unlock(String lockKey, String requireId) {
        if (StringUtils.isBlank(lockKey) || StringUtils.isBlank(requireId)) {
            throw new ShareLockException("分布式锁-解锁参数异常");
        }
        try {
            String value = redisUtil.get(lockKey);
            // 只能解锁自己的，不能解锁别人的
            if (requireId.equals(value)) {
                return redisUtil.del(lockKey);
            }
        } catch (Exception e) {
            // todo 补日志
        }
        return false;
    }

    /**
     * 尝试加锁
     *
     * 和 lock 的区别在于我是否需要自旋
     */
    public boolean tryLock(String lockKey, String requestId, Long time) {
        // 1.参数的校验
        if (StringUtils.isBlank(lockKey) || StringUtils.isBlank(requestId) || time <= 0) {
            throw new ShareLockException("分布式锁-尝试加锁参数异常");
        }
        return redisUtil.setNx(lockKey, requestId, time, TimeUnit.MILLISECONDS);
    }
}
